package com.github.zyf.ioaj.algorithm.swarmintelligence;

import com.github.zyf.ioaj.algorithm.Input;
import com.github.zyf.ioaj.algorithm.Output;
import com.github.zyf.ioaj.annotation.internal.Algorithm;
import com.github.zyf.ioaj.annotation.internal.TempParam;
import com.github.zyf.ioaj.constant.FileType;
import com.github.zyf.ioaj.constant.ParamType;
import com.github.zyf.ioaj.export.Report;
import com.github.zyf.ioaj.export.Tracker;

/**
 * <h1>Grey Wolf Optimizer</h1>
 * <p>灰狼优化算法</p>
 * <p>默认求解最小值，如果需要求解最大值，将目标函数乘以-1即可，最后得到的值再次乘以-1就是得到的最优值（自变量取值不用乘以-1）</p>
 * <p>需要计算则调用{@link #go()}</p>
 * <p>需要跟踪每次迭代则调用{@link #tracker(String, String, String, FileType)}</p>
 * <p>需要生成报告则调用{@link #report(String, String, String, FileType)}</p>
 * <p>该算法受到了灰狼捕食猎物活动的启发而开发的一种优化搜索方法，它具有较强的收敛性能、参数少、易实现等特点。</p>
 * <p>
 * 灰狼优化算法的独特之处在于一小部分拥有绝对话语权的灰狼带领一群灰狼向猎物前进。
 * 在了解灰狼优化算法的特点之前，我们有必要了解灰狼群中的等级制度。
 * </p>
 * <p>
 * 灰狼群一般分为4个等级：处于第一等级的灰狼用α表示，处于第二阶级的灰狼用β表示，
 * 处于第三阶段的灰狼用δ表示，处于第四等级的灰狼用ω表示。
 * 按照上述等级的划分，灰狼α对灰狼β、δ和ω有绝对的支配权；
 * 灰狼β对灰狼δ和ω有绝对的支配权；灰狼δ对灰狼ω有绝对的支配权。
 * <p>
 * 因为灰狼ω在灰狼群中的比例最大，同时灰狼ω又必须完全服从灰狼α、β和δ，
 * 所以灰狼群的猎食行为主要由灰狼α、β和δ进行引导和指示。
 * 在GWO中，为了模拟灰狼群的等级制度，同时又能简化算法，因此，假设各有一只灰狼α、一只灰狼β和一只灰狼δ。
 * 由此展开狩猎的三个主要步骤：寻找猎物、包围猎物和攻击猎物。
 * </p>
 *
 * @author zyf
 */
@Algorithm
public class GWO extends SwarmIntelligenceAlgorithm {

    /**
     * alpha 狼的位置
     */
    @TempParam(type = ParamType.DOUBLE_ARRAY_1, desc = "alpha 狼的位置")
    private double[] alpha_p;
    /**
     * alpha 狼的函数值
     */
    @TempParam(type = ParamType.DOUBLE, desc = "alpha 狼的函数值")
    private double alpha_f;

    /**
     * beta 狼的位置
     */
    @TempParam(type = ParamType.DOUBLE_ARRAY_1, desc = "beta 狼的位置")
    private double[] beta_p;
    /**
     * beta 狼的函数值
     */
    @TempParam(type = ParamType.DOUBLE, desc = "beta 狼的函数值")
    private double beta_f;

    /**
     * delta 狼的位置
     */
    @TempParam(type = ParamType.DOUBLE_ARRAY_1, desc = "delta 狼的位置")
    private double[] delta_p;
    /**
     * delta 狼的函数值
     */
    @TempParam(type = ParamType.DOUBLE, desc = "delta 狼的函数值")
    private double delta_f;

    /**
     * omega 狼群的位置
     */
    @TempParam(type = ParamType.DOUBLE_ARRAY_2, desc = "omega 狼群的位置")
    private double[][] omega_p;
    /**
     * omega 狼群的函数值
     */
    @TempParam(type = ParamType.DOUBLE_ARRAY_1, desc = "omega 狼群的函数值")
    private double[] omega_f;

    /**
     * 收敛因子，从 2 线性变化到 0
     */
    @TempParam(type = ParamType.DOUBLE, desc = "收敛因子")
    private double a;

    /**
     * 当前最优解对应的自变量取值
     */
    @TempParam(type = ParamType.DOUBLE_ARRAY_1, desc = "当前最优解对应的自变量取值")
    private double[] x;

    /**
     * 当前最优值
     */
    @TempParam(type = ParamType.DOUBLE, desc = "当前最优值")
    private double y;

    /**
     * 抽象算法构造函数，需要提供输入量，即提前初始化好{@link Input}对象
     *
     * @param input {@link Input}
     */
    public GWO(Input input) {
        super(input);
        init();
    }

    private void init() {
        setIter(10000);
        this.a = 2;
        int n = getN();
        int D = getInput().getVarNum();
        int i, j;
        this.alpha_p = new double[D];
        this.beta_p = new double[D];
        this.delta_p = new double[D];
        this.omega_p = new double[n][D];
        this.omega_f = new double[n];
        this.x = new double[D];
        this.alpha_f = getInput().getFunction().apply(alpha_p);
        this.beta_f = getInput().getFunction().apply(beta_p);
        this.delta_f = getInput().getFunction().apply(delta_p);
        for (i = 0; i < n; i++) {
            for (j = 0; j < D; j++) {
                this.omega_p[i][j] = getInput().randomValueInRange(j);
            }
            this.omega_f[i] = getInput().getFunction().apply(omega_p[i]);
        }
        this.x = omega_p[0].clone();
        this.y = getInput().getFunction().apply(x);
    }

    /**
     * 每次迭代的扫描逻辑
     *
     * @param t 迭代次数
     */
    private void scan(int t) {
        int i, j;
        int n = getN();
        int D = getInput().getVarNum();
        for (i = 0; i < n; i++) {
            double fitness = getInput().getFunction().apply(omega_p[i]);
            if (fitness < alpha_f) {
                alpha_f = fitness;
                alpha_p = omega_p[i].clone();
            }
            if (fitness > alpha_f && fitness < beta_f) {
                beta_f = fitness;
                beta_p = omega_p[i].clone();
            }
            if (fitness > alpha_f && fitness > beta_f && fitness < delta_f) {
                delta_f = fitness;
                delta_p = omega_p[i].clone();
            }
        }
        // 两个随机数
        double r1, r2;
        // 两个控制参数
        double A, C;
        // alpha、beta、delta三匹狼与候选狼的距离
        double D_alpha, D_beta, D_delta;
        // 候选狼根据alpha、beta、delta三匹狼得到的位置向量
        double X1, X2, X3;
        // 收缩因子
        a = 2 - t * 2.0 / getIter();
        for (i = 0; i < n; i++) {
            for (j = 0; j < D; j++) {
                // 计算 alpha 指导的位置向量
                r1 = Math.random();
                r2 = Math.random();
                A = (2 * r1 - 1) * a;
                C = 2 * r2;
                D_alpha = Math.abs(C * alpha_p[j] - omega_p[i][j]);
                X1 = alpha_p[j] - A * D_alpha;
                // 计算 beta 指导的位置向量
                r1 = Math.random();
                r2 = Math.random();
                A = (2 * r1 - 1) * a;
                C = 2 * r2;
                D_beta = Math.abs(C * beta_p[j] - omega_p[i][j]);
                X2 = beta_p[j] - A * D_beta;
                // 计算 delta 指导的位置向量
                r1 = Math.random();
                r2 = Math.random();
                A = (2 * r1 - 1) * a;
                C = 2 * r2;
                D_delta = Math.abs(C * delta_p[j] - omega_p[i][j]);
                X3 = delta_p[j] - A * D_delta;
                // 确定位置向量
                omega_p[i][j] = (X1 + X2 + X3) / 3;
                // 防止越界
                if (omega_p[i][j] < getInput().getLb()[j]) omega_p[i][j] = getInput().getLb()[j];
                if (omega_p[i][j] > getInput().getUb()[j]) omega_p[i][j] = getInput().getUb()[j];
            }
            omega_f[i] = getInput().getFunction().apply(omega_p[i]);
            if (!getInput().is_A_BetterThan_F(x, omega_f[i])) {
                x = omega_p[i].clone();
                y = omega_f[i];
            }
        }
    }

    /**
     * 生成最优解
     */
    private void generateOptimization() {
        setOutput(new Output(y, x));
    }

    /**
     * 运行并计算结果
     */
    public void go() {
        int t;
        for (t = 0; t < getIter(); t++) {
            scan(t);
        }
        generateOptimization();
    }

    /**
     * 运行并计算结果（按迭代逐步跟踪），如果目的是计算出结果，请使用{@link #go()}
     * <p>
     * 该算法迭代次数设置为10000，慎用
     *
     * @param author   作者
     * @param message  备注
     * @param title    标题
     * @param fileType 文件类型 {@link FileType}
     * @return {@link Tracker}
     */
    @Override
    public Tracker tracker(String author, String message, String title, FileType fileType) {
        int t;
        Tracker tracker = new Tracker(this, author, message, title, fileType);
        tracker.startTracker();
        for (t = 0; t < getIter(); t++) {
            scan(t);
            tracker.trackOne(t + 1);
        }
        generateOptimization();
        tracker.endTracker();
        return tracker;
    }

    /**
     * 生成报告
     *
     * @param author   作者
     * @param message  备注
     * @param title    标题
     * @param fileType 文件类型 {@link FileType}
     * @return {@link Report}
     */
    @Override
    public Report report(String author, String message, String title, FileType fileType) {
        return new Report(this, author, message, title, fileType);
    }

    public double[] getAlpha_p() {
        return alpha_p;
    }

    public double getAlpha_f() {
        return alpha_f;
    }

    public double[] getBeta_p() {
        return beta_p;
    }

    public double getBeta_f() {
        return beta_f;
    }

    public double[] getDelta_p() {
        return delta_p;
    }

    public double getDelta_f() {
        return delta_f;
    }

    public double[][] getOmega_p() {
        return omega_p;
    }

    public double[] getOmega_f() {
        return omega_f;
    }

    public double getA() {
        return a;
    }

    public double[] getX() {
        return x;
    }

    public double getY() {
        return y;
    }
}
